<template>
  <div class="upload_excel">
    <div class="uploadFile">
      <input id="excelUploadInput" ref="excelUploadInput" type="file" accept=".xlsx, .xls" @change="handleChange" />
      <div id="drop" @drop="handleDrop" @dragover="handleDragover" @dragenter="handleDragover">
        <span>拖拽文件到此处或</span>
        <el-button
          :loading="loading"
          style="margin-left: 16px;margin-top: 4px;"
          type="primary"
          @click="handleUpload"
        >{{excelData.results.length ? '重新选择' : '选择文件'}}</el-button>
        <el-button
          v-if="props.downloadShow"
          type="primary"
          style="margin-left: 10px;margin-top: 4px;"
          @click="downloadTemplate"
        >下载模板</el-button>
      </div>
    </div>
    <div v-if="props.errorHint && errorCount" class="errorHint">
      <span>导入失败！</span>
      <span>表格当中有</span>
      <span>{{errorCount}}</span>
      <span>条数据错误，请调整错误数据后重新导入。</span>
    </div>
    <el-table
      v-if="excelData.results.length"
      ref="previewTable"
      :data="excelData.results"
      border
      highlight-current-row
      height="550px"
      style="margin-top:20px;"
    >
      <template v-for="(item, key) of excelData.header">
        <el-table-column v-if="key === '异常信息'" :prop="key" :label="key" :key="key" fixed="right">
          <template #default="{row}">
            <span class="error">{{row["异常信息"]}}</span>
          </template>
        </el-table-column>
        <el-table-column
          v-else-if="item.render === 'LIST'"
          :show-overflow-tooltip="item.showOverflowTooltip"
          :prop="key"
          :label="key"
          :key="key+'elseif'"
        >
          <template #default="{row}">
            <div v-for="(v,i) in row[key]" :key="i">{{v}}</div>
          </template>
        </el-table-column>
        <el-table-column
          v-else
          :show-overflow-tooltip="item.showOverflowTooltip"
          :width="item.width"
          :prop="key"
          :label="key"
          :key="key+'else'"
        ></el-table-column>
      </template>
    </el-table>
    <div class="progress" v-if="props.progressShow">
      <div class="p_mask"></div>
      <div class="p_main">
        <div class="p_percent">
          <span :style="`left: ${props.progress * 100}%`">{{(props.progress * 100).toFixed()}}%</span>
        </div>
        <div class="p_outer">
          <div class="p_inner" :style="`width: ${props.progress * 100}%`"></div>
        </div>
        <div class="p_hint">{{props.progress >= 1 ? '导入完成' : '正在导入信息中，请勿关闭此页面'}}</div>
      </div>
    </div>
  </div>
</template>

<script setup>
import * as XLSX from 'xlsx'
import { filterObj } from '@/utils'
import { reactive, ref, watch, nextTick, getCurrentInstance } from 'vue'

const { proxy } = getCurrentInstance()

const emit = defineEmits(['excelData'])

const props = defineProps(
  {
    // 下载模板的地址
    templateUrl: {
      type: String,
      default: ''
    },
    // 是否显示下载模板按钮
    downloadShow: {
      type: Boolean,
      default: false
    },
    // 自定义下载模板事件
    downTemp: {
      type: Function,
      default: null
    },
    // 预览的时候显示的字段
    showKey: {
      type: Array,
      default: () => []
    },
    // 必须要传入的jkey
    requireKey: {
      type: Array,
      default: () => []
    },
    // 需要读取表格的范围的起点
    scopeStart: {
      type: String,
      default: ''
    },
    // 预览之前可改变表格数据
    adjustData: {
      type: Function,
      default: null
    },
    // 字段规则
    rules: {
      type: Object,
      default: () => ({})
    },
    // 限制上传的最大条数，0 表示不限制
    maxLength: {
      type: Number,
      default: 0
    },
    // 预览时的错误提示
    errorHint: {
      type: Boolean,
      default: false
    },
    // 是否显示上传进度条
    progressShow: {
      type: Boolean,
      default: false
    },
    // 上传进度
    progress: {
      type: Number,
      default: 0
    },
  }
)

const previewTable = ref(null)
const excelUploadInput = ref(null)

const loading = ref(false)
const excelData = reactive({
  header: {},
  results: []
})
let errorCount = ref(0)

watch(
  () => excelData.results,
  (to) => {
    errorCount = to.filter(v => v['异常信息']).length
  },
  {
    deep: true,
    immediate: true
  }
)

// 下载模板
function downloadTemplate() {
  if (props.downTemp) return props.downTemp(props.templateUrl)
  location.href = props.templateUrl
}
function setTableHeader(header) {
  excelData.header = header
  nextTick(() => {
    previewTable.doLayout()
  })
}
function setTableResults(results) {
  excelData.results = results
  nextTick(() => {
    previewTable.doLayout()
  })
}
// 点击文件上传
function handleUpload() {
  document.getElementById('excelUploadInput').click()
}
// 选择文件
function handleChange(e) {
  const files = e.target.files
  const rawFile = files[0]
  if (!rawFile) return
  upload(rawFile)
}
function upload(rawFile) {
  excelUploadInput.value = null // fix can't select the same excel
  readerData(rawFile)
}
function readerData(rawFile) {
  loading.value = true
  return new Promise((resolve) => {
    const reader = new FileReader()
    reader.onload = (e) => {
      const data = e.target.result
      const fixedData = fixdata(data)
      const workbook = XLSX.read(btoa(fixedData), { type: 'base64' })
      if (props.scopeStart) workbook.Sheets[workbook.SheetNames[0]]['!ref'] = workbook.Sheets[workbook.SheetNames[0]]['!ref'].replace('A1', props.scopeStart)
      const firstSheetName = workbook.SheetNames[0]
      const worksheet = workbook.Sheets[firstSheetName]
      const header = {}
      get_header_row(worksheet).forEach(v => { header[v] = {} })
      const results = XLSX.utils.sheet_to_json(worksheet)
      generateDate({ header, results })
      loading.value = false
      resolve()
    }
    reader.readAsArrayBuffer(rawFile)
  })
}
function fixdata(data) {
  let o = ''
  let l = 0
  const w = 10240
  for (; l < data.byteLength / w; ++l) o += String.fromCharCode.apply(null, new Uint8Array(data.slice(l * w, l * w + w)))
  o += String.fromCharCode.apply(null, new Uint8Array(data.slice(l * w)))
  return o
}
function get_header_row(sheet) {
  const headers = []
  const range = XLSX.utils.decode_range(sheet['!ref'])
  let C
  const R = range.s.r /* start in the first row */
  for (C = range.s.c; C <= range.e.c; ++C) { /* walk every column in the range */
    var cell = sheet[XLSX.utils.encode_cell({ c: C, r: R })] /* find the cell in the first row */
    var hdr = 'UNKNOWN ' + C // <-- replace with your desired default
    if (cell && cell.t) hdr = XLSX.utils.format_cell(cell)
    headers.push(hdr)
  }
  return headers
}
function generateDate({ header, results }) {
  if (props.maxLength && results.length > props.maxLength) return proxy.$message.error(`请上传条数${props.maxLength}以内的表格`)
  errorCount = 0
  // 校验字段必须性以及字段规则
  if (props.requireKey.length || Object.keys(props.rules).length) {
    for (let i = 0; i < results.length; i++) {
      // 字段必须性
      if (props.requireKey.length) {
        for (let j = 0; j < props.requireKey.length; j++) {
          if (!results[i][props.requireKey[j]] && results[i][props.requireKey[j]] !== 0) return proxy.$message.error(`表格异常：第${i + 1}行 - ${props.requireKey[j]}字段异常`)
        }
      }
      // 字段规则
      if (Object.keys(props.rules).length) {
        for (const key in props.rules) {
          if (props.rules[key] === 'number' && (isNaN(parseFloat(results[i][key])) || isNaN(Number(results[i][key])))) return proxy.$message.error(`表格异常：第${i + 1}行 - ${key}字段必须是数值`)
        }
      }
    }
  }
  if (props.adjustData) {
    const newData = props.adjustData({ header, results })
    if (newData.err) return proxy.$message.error(`表格异常：第${newData.err.line}行 - ${newData.err.msg}`)
    header = newData.header
    results = newData.results
  }

  // 筛选出要显示的字段
  if (props.showKey.length) {
    header = filterObj(header, props.showKey, obj => { for (const key in obj) if (!obj[key]) obj[key] = {} })
    results = results.map(v => {
      const newV = {}
      for (const key in header) newV[key] = v[key]
      return newV
    })
  }
  excelData.header = header
  excelData.results = results
  emit('excelData', { header, results })
}

function handleDrop(e) {
  e.stopPropagation()
  e.preventDefault()
  if (loading.value) return
  const files = e.dataTransfer.files
  if (files.length !== 1) {
    proxy.$message.error('Only support uploading one file!')
    return
  }
  const rawFile = files[0] // only use files[0]
  upload(rawFile)
  e.stopPropagation()
  e.preventDefault()
}
function handleDragover(e) {
  e.stopPropagation()
  e.preventDefault()
  e.dataTransfer.dropEffect = 'copy'
}

</script>

<style lang="scss" scoped>
#excelUploadInput {
  display: none;
  z-index: -9999;
}
#drop {
  border: 2px dashed #bbb;
  box-sizing: border-box;
  width: 100%;
  height: 160px;
  margin: 0 auto;
  font-size: 20px;
  border-radius: 5px;
  color: #bbb;
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
}
.errorHint {
  text-align: left;
  color: #333;
  font-size: 16px;
  margin-top: 20px;
}
.errorHint span:nth-of-type(1),
.errorHint span:nth-of-type(3) {
  color: red;
}
.upload_excel {
  position: relative;
  .progress {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    > .p_mask {
      background-color: rgba(255, 255, 255, 0.5);
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100%;
    }
    > .p_main {
      width: 60%;
      position: absolute;
      left: 50%;
      top: 50%;
      transform: translate(-50%, -50%);
      > .p_percent {
        width: calc(100% - 30px);
        text-align: left;
        margin: 0 auto;
        span {
          position: relative;
          left: 0%;
          transition: all linear 0.3s;
          display: inline-block;
          transform: translateX(-50%);
          font-size: 18px;
          font-weight: bold;
          color: #2c3146;
          line-height: 25px;
        }
      }
      > .p_outer {
        width: 100%;
        height: 12px;
        background-color: #f5f6fa;
        border-radius: 8px;
        overflow: hidden;
        .p_inner {
          width: 0%;
          transition: all linear 0.3s;
          height: 100%;
          border-radius: 8px;
          background: #2c3146;
        }
      }
      > .p_hint {
        margin-top: 10px;
        font-size: 14px;
        color: #2c3146;
        line-height: 20px;
      }
    }
  }
}
</style>
<style>
.upload_excel .error {
  color: red;
  font-weight: bold;
}
</style>